/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Description:
 *   Brute force. The string commands are parsed and checked for validity
 *   in x_gui.c.
 */

#include <math.h>
#include "x_brute.h"
#include "x_main.h"
#include "x_saves.h"
#include "doomstat.h"
#include "g_game.h"
#include "m_random.h"

extern int xDoneDamage;
extern int xLineCrossed;
extern int xUseSuccess;

static void x_countChecks(struct brutecheck ch);
static int x_bruteCheck(struct brutecheck ch);
static inline void x_bruteSet(int tic, void **val, short pos, char type,
  unsigned char plr_offset);
static void x_bruteForward(int numtics);
static void x_bruteLoad(int tic);

static unsigned int numcmp;
static double xPlayerSpeed;
static unsigned char displayplayer_save; // displayplayer value to restore

static int *cmp_to[SPEED] = {
    &xLineCrossed,
    &rng.rndindex,
    &xDoneDamage
    // the rest will be set by x_setChecks
};
static double *cmp_to_d[MAXVALUES - SPEED] = {&xPlayerSpeed};

static int x_sortTics(const void *l, const void *r)
{
    int tic1 = ((const struct brutecmd*)l)->tic,
      tic2 = ((const struct brutecmd *)r)->tic;
    if (tic1 < tic2)
        return -1;
    if (tic1 > tic2)
        return 1;
    return 0;
}

int x_startBrute(struct brutecmd cmds[], int len, struct brutecheck check)
{
    x_countChecks(check);
    if (!numcmp)
        return true;
    qsort(cmds, len, sizeof(cmds[0]), x_sortTics);
    x_clearMapSavepoints();
    x_clearUserSavepoint();
    gametic = 0;
    gameaction = ga_nothing;
    G_ReadDemoHeader(header, header_size);
    x_bruteForward(cmds[0].tic - 1);
    if (gamestate == GS_LEVEL)
        x_makeSavepoint(true);
    else
        return false;
    xUseSuccess = xLineCrossed = xDoneDamage = 0;
    if (x_doBrute(cmds, 0, len, check)) {
        numcmp = 0;
        x_goBack(0);
        x_display();
        return true;
    }
    numcmp = 0;
    x_goBack(0);
    x_display();
    return false;
}

#define BRUTE_SET(a) x_bruteSet(cmds[pos].tic, (void **)((char *)&cmds[pos] + \
  ch.cmd_offset[a]), i[a], ch.type[a], ch.plr_offset[a])
#define BRUTE_LOOP(a) for (i[a] = 0; i[a] < *(short *)((char *)&cmds[pos] + \
  ch.len_offset[a]); i[a]++)
// the recursive crap
int x_doBrute(struct brutecmd cmds[], int pos, int len, struct brutecheck ch)
{
    short i[MAXTICCMDS];
    int entertic = gametic, nextpos = pos + 1;
    // there doesn't seem to be a way to use less loops...
    BRUTE_LOOP(0) {
        BRUTE_SET(0);
        BRUTE_LOOP(1) {
            BRUTE_SET(1);
            BRUTE_LOOP(2) {
                BRUTE_SET(2);
                BRUTE_LOOP(3) {
                    BRUTE_SET(3);
                    // everything is set now
                    if (nextpos < len && cmds[nextpos].tic <= ch.tic) {
                        x_bruteForward(cmds[nextpos].tic - gametic);
                        if (x_doBrute(cmds, nextpos, len, ch))
                            return true;
                    } else {
                        x_bruteForward(ch.tic - gametic);
                        if (x_bruteCheck(ch))
                            return true;
                    }
                    x_bruteLoad(entertic);
                }
            }
        }
    }
    return false;
}
#undef BRUTE_SET
#undef BRUTE_LOOP

static inline void x_bruteSet(int tic, void **val, short pos, char type,
  unsigned char plr_offset)
{
    if (!type)
        *((signed char *)&tics[displayplayer][tic - 1] + plr_offset) =
          (*(signed char **)val)[pos];
    else
        *(signed short *)((char *)&tics[displayplayer][tic - 1] + plr_offset) =
          (*(signed short **)val)[pos];
}

static void x_bruteForward(int numtics)
{
    if (numtics < 1)
        return;
    while (numtics-- > 0) {
        xUseSuccess = xLineCrossed = xDoneDamage = 0;
        x_makeCmds();
        G_Ticker();
        ++gametic;
    }
}

static void x_bruteLoad(int tic)
{
    if (tic == gametic)
        return;
    if (!x_loadSavepoint(gametic + 1)) {
        x_bruteForward(tic - gametic);
    } else {
        gametic = 0;
        gameaction = ga_nothing;
        G_ReadDemoHeader(header, header_size);
        x_bruteForward(tic);
    }
}

static void x_countChecks(struct brutecheck ch)
{
    int i, j;
    unsigned char p = ch.player;
    displayplayer_save = displayplayer;
    displayplayer = p;
    numcmp += ch.usecheck;
    for (i = 0; i < MAXVALUES; i++)
        for (j = 0; j < MAX_CMP_VALUES; j++)
            if (ch.cmp[i][j])
                numcmp++;
            else
                break;
}

// FIXME? player[p].mo->* changes constantly
static inline void x_setPtrs(void)
{
    cmp_to[X_POS] = &players[displayplayer].mo->x;
    cmp_to[Y_POS] = &players[displayplayer].mo->y;
    cmp_to[Z_POS] = &players[displayplayer].mo->z;
    cmp_to[X_MOM] = &players[displayplayer].mo->momx;
    cmp_to[Y_MOM] = &players[displayplayer].mo->momy;
}

static int x_bruteCheck(struct brutecheck ch)
{
    int i, j, nc = numcmp;
    x_setPtrs();
    if (ch.cmp[SPEED][0])
        xPlayerSpeed = sqrt(*cmp_to[X_POS] * *cmp_to[X_POS] + *cmp_to[Y_POS] *
          *cmp_to[Y_POS]);
    nc -= ch.usecheck && ch.usecheck == xUseSuccess;
    for (i = 0; i < SPEED; i++)
        for (j = 0; j < MAX_CMP_VALUES; j++) {
            if (!ch.cmp[i][j])
                break;
            switch (ch.cmp[i][j]) {
            case CMP_EQU:
                nc -= *cmp_to[i] == ch.values[i][j];
                break;
            case CMP_NEQ:
                nc -= *cmp_to[i] != ch.values[i][j];
                break;
            case CMP_GEQ:
                nc -= *cmp_to[i] >= ch.values[i][j];
                break;
            case CMP_GTR:
                nc -= *cmp_to[i] > ch.values[i][j];
                break;
            case CMP_LEQ:
                nc -= *cmp_to[i] <= ch.values[i][j];
                break;
            case CMP_LSS:
                nc -= *cmp_to[i] < ch.values[i][j];
                break;
            }
        }
    // TODO: change when more doubles are added
    for (j = 0; j < MAX_CMP_VALUES; j++) {
        if (!ch.cmp[SPEED][j])
            break;
        switch (ch.cmp[SPEED][j]) {
        case CMP_EQU:
            nc -= *cmp_to_d[0] == ch.values_d[0][j];
            break;
        case CMP_NEQ:
            nc -= *cmp_to_d[0] != ch.values_d[0][j];
            break;
        case CMP_GEQ:
            nc -= *cmp_to_d[0] >= ch.values_d[0][j];
            break;
        case CMP_GTR:
            nc -= *cmp_to_d[0] > ch.values_d[0][j];
            break;
        case CMP_LEQ:
            nc -= *cmp_to_d[0] <= ch.values_d[0][j];
            break;
        case CMP_LSS:
            nc -= *cmp_to_d[0] < ch.values_d[0][j];
            break;
        }
    }
    if (!nc) {
        displayplayer = displayplayer_save;
        return true;
    }
    return false;
}

